package org.jsets.fastboot.persistent.config;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import org.javers.core.metamodel.annotation.Entity;
import org.jsets.fastboot.util.ReflectUtils;
import org.jsets.fastboot.util.SpringContextHolder;
import org.jsets.fastboot.util.StringUtils;
import org.jsets.fastboot.persistent.annotation.DataScope;
import org.jsets.fastboot.persistent.annotation.FieldBind;
import org.jsets.fastboot.persistent.annotation.FieldDesensitize;
import org.jsets.fastboot.persistent.annotation.FieldEncrypt;
import org.jsets.fastboot.persistent.annotation.FieldFill;
import org.jsets.fastboot.persistent.metadata.DataScopeOption;
import org.jsets.fastboot.persistent.metadata.FieldBindOption;
import org.jsets.fastboot.persistent.metadata.FieldDesensitizeOption;
import org.jsets.fastboot.persistent.metadata.FieldEncryptOption;
import org.jsets.fastboot.persistent.metadata.FieldFillOption;
import org.jsets.fastboot.persistent.metadata.PersistentOption;
import org.jsets.fastboot.persistent.metadata.DataScopeOption.DataColumn;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PersistentAnnotationResolver {

	protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
	protected static final Map<String, PersistentOption> OPTIONS = Maps.newConcurrentMap();
	protected static final Map<String, DataScopeOption> SCOPES = Maps.newConcurrentMap();

	/**
	 * @param typeAliasesPackage
	 */
	public static void resolveEntityPackage(String typeAliasesPackage) {
		ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
		MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
		try {
			for (String aliasesPackage : typeAliasesPackage.split(",")) {
				aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/"+ DEFAULT_RESOURCE_PATTERN;
				Resource[] resources = resolver.getResources(aliasesPackage);
				if (resources != null && resources.length > 0) {
					MetadataReader metadataReader = null;
					for (Resource resource : resources) {
						if (resource.isReadable()) {
							metadataReader = metadataReaderFactory.getMetadataReader(resource);
							Class<?> clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
							PersistentOption option = new PersistentOption();
							boolean javersEnabled = StringUtils.toBoolean(SpringContextHolder.getProperty("persistent.javers.enabled","false"));
							if(javersEnabled) {
								Entity entityAnn = clazz.getAnnotation(Entity.class);
								if (entityAnn != null) {
									option.setAuditable(true);
								}
							}
							Field[] fields = ReflectUtil.getFields(clazz);
							for (Field field : fields) {
								if (ReflectUtils.isStatic(field.getModifiers())) {
									continue;
								}

								Annotation fieldFillAnn = field.getAnnotation(FieldFill.class);
								if (fieldFillAnn != null) {
									option.setFillEnable(true);
									FieldFill fieldFill = (FieldFill) fieldFillAnn;
									FieldFillOption fieldFillOption = new FieldFillOption();
									fieldFillOption.setField(field.getName());
									fieldFillOption.setSqlAction(fieldFill.when());
									option.addFieldFillOption(fieldFillOption);
								}

								Annotation fieldBindAnn = field.getAnnotation(FieldBind.class);
								if (fieldBindAnn != null) {
									option.setBindEnable(true);
									FieldBind fieldBind = (FieldBind) fieldBindAnn;
									FieldBindOption fieldBindOption = new FieldBindOption();
									fieldBindOption.setBindType(fieldBind.type());
									fieldBindOption.setBindField(field.getName());
									String target = fieldBind.target();
									Field targetField = ReflectUtil.getField(clazz, target);
									Objects.requireNonNull(targetField, "没有[" + target + "]字段");
									fieldBindOption.setTargetField(targetField.getName());
									option.addFieldBindOption(fieldBindOption);
								}

								FieldEncrypt fieldEncryptAnn = field.getAnnotation(FieldEncrypt.class);
								if (fieldEncryptAnn != null) {
									option.setEncryptEnable(true);
									FieldEncrypt fieldEncrypt = (FieldEncrypt) fieldEncryptAnn;
									FieldEncryptOption fieldEncryptOption = new FieldEncryptOption();
									fieldEncryptOption.setField(field.getName());
									fieldEncryptOption.setAlgorithm(fieldEncrypt.algorithm());
									fieldEncryptOption.setLazy(fieldEncrypt.lazy());
									fieldEncryptOption.setEncryptOnStore(fieldEncrypt.encryptOnStore());
									fieldEncryptOption.setEncryptOnQuery(fieldEncrypt.encryptOnQuery());
									fieldEncryptOption.setDecryptOnQuery(fieldEncrypt.decryptOnQuery());
									option.addFieldEncryptOption(fieldEncryptOption);
								}

								FieldDesensitize fieldDesensitizeAnn = field.getAnnotation(FieldDesensitize.class);
								if (fieldDesensitizeAnn != null) {
									option.setDesensitizeEnable(true);
									FieldDesensitize fieldDesensitize = (FieldDesensitize) fieldDesensitizeAnn;
									FieldDesensitizeOption fieldDesensitizeOption = new FieldDesensitizeOption();
									fieldDesensitizeOption.setField(field.getName());
									fieldDesensitizeOption.setFormat(fieldDesensitize.format());
									fieldDesensitizeOption.setLazy(fieldDesensitize.lazy());
									option.addFieldDesensitizeOption(fieldDesensitizeOption);
								}
							}

							if (option.isFillEnable() 
									|| option.isBindEnable() 
									|| option.isEncryptEnable()
									|| option.isDesensitizeEnable() 
									|| option.isAuditable()) {
								OPTIONS.put(clazz.getName(), option);
								log.info(clazz + ": FieldFill[{}]、FieldBind[{}]、FieldEncrypt[{}]、FieldDesensitize[{}]、Audit[{}]", 
										option.isFillEnable(), option.isBindEnable(), option.isEncryptEnable(), option.isDesensitizeEnable(), option.isAuditable());
							}
						}
					}
				}
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	/**
	 * @param typeMapperPackage
	 */
	public static void resolveMapperPackage(String typeMapperPackage) {
		ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
		MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
		try {
			for (String aliasesPackage : typeMapperPackage.split(",")) {
				aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
				Resource[] resources = resolver.getResources(aliasesPackage);
				if (resources != null && resources.length > 0) {
					MetadataReader metadataReader = null;
					for (Resource resource : resources) {
						if (resource.isReadable()) {
							metadataReader = metadataReaderFactory.getMetadataReader(resource);
							Class<?> clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
							for (Method method : clazz.getMethods()) {
								DataScope dataScopeAnn = method.getAnnotation(DataScope.class);
								if (null != dataScopeAnn) {
									DataScopeOption scope = new DataScopeOption();
									scope.setType(dataScopeAnn.type());
									if(null!=dataScopeAnn.columns()&&dataScopeAnn.columns().length>0) {
										List<DataColumn> dcs = Lists.newArrayList();
										Stream.of(dataScopeAnn.columns()).forEach(column->{
											DataColumn dc = new DataColumn(column.alias(), column.name());
											dcs.add(dc);
										});
										scope.setColumns(dcs);
									}
									String methodName = clazz.getName()+"."+method.getName();
									SCOPES.put(methodName, scope);
									log.info(methodName + " data scope enable");
								}
							}
						}
					}
				}
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	public static PersistentOption getOption(Class<?> clazz) {
		return OPTIONS.get(clazz.getName());
	}

	public static boolean includeOption(Class<?> clazz) {
		return OPTIONS.containsKey(clazz.getName());
	}
	
	public static boolean dataScopeEnbale(String methodName) {
		return SCOPES.containsKey(methodName);
	}
	
	public static DataScopeOption getDataScope(String methodName) {
		return SCOPES.get(methodName);
	}

}